/* cpu.c++
 * detects x86 CPU features
 * many thanks to http://wiki.osdev.org/CPUID for the constants.
 * (which are apparently public domain and derived from Clicker
 * http://clicker.sourceforge.net/)
 */

#include <cstdio>
#include <cstring>
#include <stdint.h>
#include <unistd.h>
#include "cpu.h"

#define CPUID_VENDOR_OLDAMD       "AMDisbetter!"
#define CPUID_VENDOR_AMD          "AuthenticAMD"
#define CPUID_VENDOR_INTEL        "GenuineIntel"
#define CPUID_VENDOR_VIA          "CentaurHauls"
#define CPUID_VENDOR_OLDTRANSMETA "TransmetaCPU"
#define CPUID_VENDOR_TRANSMETA    "GenuineTMx86"
#define CPUID_VENDOR_CYRIX        "CyrixInstead"
#define CPUID_VENDOR_CENTAUR      "CentaurHauls"
#define CPUID_VENDOR_NEXGEN       "NexGenDriven"
#define CPUID_VENDOR_UMC          "UMC UMC UMC "
#define CPUID_VENDOR_SIS          "SiS SiS SiS "
#define CPUID_VENDOR_NSC          "Geode by NSC"
#define CPUID_VENDOR_RISE         "RiseRiseRise"

enum {
    CPUID_FEAT_ECX_SSE3         = 1 << 0, 
    CPUID_FEAT_ECX_PCLMUL       = 1 << 1,
    CPUID_FEAT_ECX_DTES64       = 1 << 2,
    CPUID_FEAT_ECX_MONITOR      = 1 << 3,  
    CPUID_FEAT_ECX_DS_CPL       = 1 << 4,  
    CPUID_FEAT_ECX_VMX          = 1 << 5,  
    CPUID_FEAT_ECX_SMX          = 1 << 6,  
    CPUID_FEAT_ECX_EST          = 1 << 7,  
    CPUID_FEAT_ECX_TM2          = 1 << 8,  
    CPUID_FEAT_ECX_SSSE3        = 1 << 9,  
    CPUID_FEAT_ECX_CID          = 1 << 10,
    CPUID_FEAT_ECX_FMA          = 1 << 12,
    CPUID_FEAT_ECX_CX16         = 1 << 13, 
    CPUID_FEAT_ECX_ETPRD        = 1 << 14, 
    CPUID_FEAT_ECX_PDCM         = 1 << 15, 
    CPUID_FEAT_ECX_DCA          = 1 << 18, 
    CPUID_FEAT_ECX_SSE4_1       = 1 << 19, 
    CPUID_FEAT_ECX_SSE4_2       = 1 << 20, 
    CPUID_FEAT_ECX_x2APIC       = 1 << 21, 
    CPUID_FEAT_ECX_MOVBE        = 1 << 22, 
    CPUID_FEAT_ECX_POPCNT       = 1 << 23, 
    CPUID_FEAT_ECX_AES          = 1 << 25, 
    CPUID_FEAT_ECX_XSAVE        = 1 << 26, 
    CPUID_FEAT_ECX_OSXSAVE      = 1 << 27, 
    CPUID_FEAT_ECX_AVX          = 1 << 28,
 
    CPUID_FEAT_EDX_FPU          = 1 << 0,  
    CPUID_FEAT_EDX_VME          = 1 << 1,  
    CPUID_FEAT_EDX_DE           = 1 << 2,  
    CPUID_FEAT_EDX_PSE          = 1 << 3,  
    CPUID_FEAT_EDX_TSC          = 1 << 4,  
    CPUID_FEAT_EDX_MSR          = 1 << 5,  
    CPUID_FEAT_EDX_PAE          = 1 << 6,  
    CPUID_FEAT_EDX_MCE          = 1 << 7,  
    CPUID_FEAT_EDX_CX8          = 1 << 8,  
    CPUID_FEAT_EDX_APIC         = 1 << 9,  
    CPUID_FEAT_EDX_SEP          = 1 << 11, 
    CPUID_FEAT_EDX_MTRR         = 1 << 12, 
    CPUID_FEAT_EDX_PGE          = 1 << 13, 
    CPUID_FEAT_EDX_MCA          = 1 << 14, 
    CPUID_FEAT_EDX_CMOV         = 1 << 15, 
    CPUID_FEAT_EDX_PAT          = 1 << 16, 
    CPUID_FEAT_EDX_PSE36        = 1 << 17, 
    CPUID_FEAT_EDX_PSN          = 1 << 18, 
    CPUID_FEAT_EDX_CLF          = 1 << 19, 
    CPUID_FEAT_EDX_DTES         = 1 << 21, 
    CPUID_FEAT_EDX_ACPI         = 1 << 22, 
    CPUID_FEAT_EDX_MMX          = 1 << 23, 
    CPUID_FEAT_EDX_FXSR         = 1 << 24, 
    CPUID_FEAT_EDX_SSE          = 1 << 25, 
    CPUID_FEAT_EDX_SSE2         = 1 << 26, 
    CPUID_FEAT_EDX_SS           = 1 << 27, 
    CPUID_FEAT_EDX_HTT          = 1 << 28, 
    CPUID_FEAT_EDX_TM1          = 1 << 29, 
    CPUID_FEAT_EDX_IA64         = 1 << 30,
    CPUID_FEAT_EDX_PBE          = 1 << 31
};

static void cpuid(uint32_t ieax, uint32_t iecx, uint32_t *eax, uint32_t *ebx, uint32_t *ecx, uint32_t *edx);
static bool hasCpuid();

static CpuInfo cpuInfo;

void initCpuInfo() {
	uint32_t maxStandard, maxExtended;
	uint32_t eax, ebx, ecx, edx;
	(void) eax; (void) ebx; (void) ecx; (void) edx; // avoid unused var warnings
	memset(&cpuInfo, 0, sizeof(CpuInfo));
	
	/* linux-only code */
	/* for other archs try:
	http://stackoverflow.com/questions/150355/programmatically-find-the-number-of-cores-on-a-machine
	*/
	cpuInfo.nCpus = sysconf(_SC_NPROCESSORS_ONLN);
	
	/* shouldnt happen */
	if (cpuInfo.nCpus < 1)
		cpuInfo.nCpus = 1;
	
	
	if (!hasCpuid()) {
		cpuInfo.hasCpuId = false;
		return;
	} else {
		cpuInfo.hasCpuId = true;
	}
	
	/* get maximum cpuid levels */
	cpuid(0, 0, &maxStandard, NULL, NULL, NULL);
	cpuid(0x80000000, 0, &maxExtended, NULL, NULL, NULL);
	
	/* get vendor id */
	cpuid(0, 0, NULL, (uint32_t *)cpuInfo.vendorId, (uint32_t *)(cpuInfo.vendorId + 8), (uint32_t *)(cpuInfo.vendorId + 4));
	cpuInfo.vendorId[12] = '\0';
	if (strcmp(cpuInfo.vendorId, CPUID_VENDOR_INTEL) == 0)
		cpuInfo.isIntel = 1;
	else
		cpuInfo.isIntel = 0;
	if (strcmp(cpuInfo.vendorId, CPUID_VENDOR_AMD) == 0)
		cpuInfo.isAmd = 1;
	else
		cpuInfo.isAmd = 0;
	
	/* get the brand */
	if (maxExtended >= 0x80000004) {
		cpuid(0x80000002, 0, (uint32_t *)cpuInfo.brand, (uint32_t *)(cpuInfo.brand + 4), (uint32_t *)(cpuInfo.brand + 8), (uint32_t *)(cpuInfo.brand + 12));
		cpuid(0x80000003, 0, (uint32_t *)(cpuInfo.brand + 16), (uint32_t *)(cpuInfo.brand + 20), (uint32_t *)(cpuInfo.brand + 24), (uint32_t *)(cpuInfo.brand + 28));
		cpuid(0x80000004, 0, (uint32_t *)(cpuInfo.brand + 32), (uint32_t *)(cpuInfo.brand + 36), (uint32_t *)(cpuInfo.brand + 40), (uint32_t *)(cpuInfo.brand + 44));
		cpuInfo.brand[48] = '\0';
	} else {
		strcpy (cpuInfo.brand, "Unknown");
	}
	
	/* get relevant cpu features */
	if (maxStandard >= 0x00000001) {
		cpuid(0x00000001, 0, NULL, NULL, &ecx, &edx);
		cpuInfo.hasMmx = edx & CPUID_FEAT_EDX_MMX;
		cpuInfo.hasSse = edx & CPUID_FEAT_EDX_SSE;
		cpuInfo.hasSse2 = edx & CPUID_FEAT_EDX_SSE2;
		cpuInfo.hasHt = edx & CPUID_FEAT_EDX_HTT;
		
		cpuInfo.hasSse3 = ecx & CPUID_FEAT_ECX_SSE3;
		cpuInfo.hasSsse3 = ecx & CPUID_FEAT_ECX_SSSE3;
		cpuInfo.hasSse4_1 = ecx & CPUID_FEAT_ECX_SSE4_1;
		cpuInfo.hasSse4_2 = ecx & CPUID_FEAT_ECX_SSE4_2;
		cpuInfo.hasAvx = ecx & CPUID_FEAT_ECX_AVX;
		cpuInfo.hasFma = ecx & CPUID_FEAT_ECX_FMA;
	}

}

void dumpCpuInfo() {
	printf("- cpu info-\n");
	printf("Processors: %d\n", cpuInfo.nCpus);
	if (cpuInfo.hasCpuId == false) {
		printf("\tcpuid not supported. could not determine info\n");
		return;
	}
	printf("\tvendor: %s\n", cpuInfo.vendorId);
	printf("\tbrand: %s\n", cpuInfo.brand);
	printf("\tfeatures:\n");
	printf("\t\t%8s: %d\n", "MMX", (int)cpuInfo.hasMmx);
	printf("\t\t%8s: %d\n", "SSE", (int)cpuInfo.hasSse);
	printf("\t\t%8s: %d\n", "SSE2", (int)cpuInfo.hasSse2);
	printf("\t\t%8s: %d\n", "SSE3", (int)cpuInfo.hasSse3);
	printf("\t\t%8s: %d\n", "SSSE3", (int)cpuInfo.hasSsse3);
	printf("\t\t%8s: %d\n", "SSE4.1", (int)cpuInfo.hasSse4_1);
	printf("\t\t%8s: %d\n", "SSE4.2", (int)cpuInfo.hasSse4_2);
	printf("\t\t%8s: %d\n", "AVX", (int)cpuInfo.hasAvx);
	printf("\t\t%8s: %d\n", "FMA", (int)cpuInfo.hasFma);
	printf("\t\t%8s: %d\n", "HT", (int)cpuInfo.hasHt);

}

CpuInfo* getCpuInfo() {
	return &cpuInfo;
}

static bool hasCpuid() {
#if defined(__i386__)
	uint32_t has;
	asm("pushfl\n"
		"popl %%eax\n"
		"movl %%eax, %%ebx\n"
		"xorl $(1 << 21), %%eax\n"
		"pushl %%eax\n"
		"popfl\n"
		"pushfl\n"
		"popl %%eax\n"
		"andl $(1 << 21), %%eax\n"
		"andl $(1 << 21), %%ebx\n"
		"cmpl %%eax, %%ebx\n"
		"je .noCpuid\n"
		"movl $01, %0\n"
		"jmp .end\n"
		".noCpuid:\n"
		"movl $00, %0\n"
		".end:\n"
		:"=r"(has)
		:
		:"%eax", "%ebx"
		);
	return has;
#elif defined(__amd64__)
	return true;
#else
	return false;
#endif
}

static void cpuid(uint32_t ieax, uint32_t iecx, uint32_t *eax, uint32_t *ebx, uint32_t *ecx, uint32_t *edx) {
	uint32_t a, b, c, d;
	asm("movl %4, %%eax\n" 
		"movl %5, %%ecx\n"
		"cpuid\n"
		"movl %%eax, %0\n"
		"movl %%ebx, %1\n"
		"movl %%ecx, %2\n"
		"movl %%edx, %3\n"
		:"=m"(a), "=m"(b), "=m"(c), "=m"(d)	// output
		:"r"(ieax), "r"(iecx)				// input
		:"%eax","%ebx","%ecx","%edx"		// clobber list
		);
	if (eax != NULL)
		*eax = a;
	if (ebx != NULL)
		*ebx = b;
	if (ecx != NULL)
		*ecx = c;
	if (edx != NULL)
		*edx = d;		
}
